package io.knifer.gokexceltask.excel.handler;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.google.common.base.Joiner;
import io.knifer.gokexceltask.dao.ClasseDAO;
import io.knifer.gokexceltask.dao.FacultiesDAO;
import io.knifer.gokexceltask.dao.MajorDAO;
import io.knifer.gokexceltask.dao.StudentDAO;
import io.knifer.gokexceltask.excel.converter.IdCardNumberEncryptConverter;
import io.knifer.gokexceltask.excel.converter.LocalDateTimeConverter;
import io.knifer.gokexceltask.excel.listener.StudentDataListener;
import io.knifer.gokexceltask.excel.validator.StudentDataValidator;
import io.knifer.gokexceltask.pojo.domain.Classe;
import io.knifer.gokexceltask.pojo.domain.Faculties;
import io.knifer.gokexceltask.pojo.domain.Major;
import io.knifer.gokexceltask.pojo.domain.Student;
import io.knifer.gokexceltask.utils.AESUtils;
import io.knifer.gokexceltask.utils.FileUtils;
import io.knifer.gokexceltask.utils.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;

import javax.annotation.concurrent.ThreadSafe;
import java.io.File;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Component
@Slf4j
@ThreadSafe
public class StudentDataExcelHandler {
    private final StudentDAO studentDAO;
    private final ClasseDAO classeDAO;
    private final MajorDAO majorDAO;
    private final FacultiesDAO facultiesDAO;
    private final StudentDataValidator validator;
    private final Joiner joiner;
    private final ThreadPoolExecutor executor;
    private final static int THREAD_NUM = 5;

    public StudentDataExcelHandler(StudentDAO studentDAO, ClasseDAO classeDAO, MajorDAO majorDAO, FacultiesDAO facultiesDAO, StudentDataValidator validator, Joiner joiner) {
        this.studentDAO = studentDAO;
        this.classeDAO = classeDAO;
        this.majorDAO = majorDAO;
        this.facultiesDAO = facultiesDAO;
        this.validator = validator;
        this.joiner = joiner;
        this.executor = new ThreadPoolExecutor(
                THREAD_NUM, THREAD_NUM, 1, TimeUnit.SECONDS,
                new SynchronousQueue<>()
        );
    }

    public Map<String, String> importByExcel(File excelFile, boolean allowMiscDataAutoInsert){
        // key: 行号；value: 错误信息
        Map<String, List<String>> validateMsg = HttpUtils.getSessionAttr("validate_msg", Map.class);
        int startRowNum = 1;
        CountDownLatch countDownLatch;

        do{
            countDownLatch = new CountDownLatch(THREAD_NUM);
            for (int i = 0; i < THREAD_NUM; i++){
                executor.execute(new StudentDataImportAction(
                        allowMiscDataAutoInsert,
                        excelFile,
                        startRowNum,
                        countDownLatch,
                        validateMsg
                ));
                startRowNum += StudentDataListener.MAX_SIZE;
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
            }
        }while (StudentDataListener.needMore());

        return validateMsg.entrySet()
                .stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> joiner.join(entry.getValue())
                ));
    }

    public void exportAsExcel(UUID fileName) throws IOException {
        String filePath = "temp" + File.separator + fileName + ".xlsx";
        long count = studentDAO.count();
        int numEachPage = 500;
        long numInFor;
        int pageInFor;
        List<Student> students;
        File file = FileUtils.getResourceFile(filePath);
        WriteSheet writeSheet;

        try (ExcelWriter excelWriter = EasyExcel.write(file, Student.class).build()) {
            writeSheet = EasyExcel.writerSheet("学生").build();
            // 分页查询，分批写入
            for (
                    numInFor = 0, pageInFor = 0;
                    numInFor <= count;
                    numInFor += numEachPage, pageInFor++
            ) {
                students = studentDAO.findAll(PageRequest.of(pageInFor, numEachPage)).getContent();
                excelWriter.write(students, writeSheet);
            }
        }
    }

    public void mockAsExcel(UUID fileName, int rows) throws IOException {
        String filePath = "temp" + File.separator + fileName + ".xlsx";
        File file = FileUtils.getResourceFile(filePath);
        WriteSheet writeSheet;
        int numEachPage = 500;
        int now = 0;
        ArrayList<Student> list = new ArrayList<>(numEachPage);

        try (ExcelWriter excelWriter = EasyExcel.write(file, Student.class).build()) {
            writeSheet = EasyExcel.writerSheet("学生").build();
            while (rows > 0){
                numEachPage = Math.min(rows, numEachPage);
                IntStream.range(now, now + numEachPage).forEach(i -> {
                    try {
                        list.add(randomStudent());
                    } catch (InvalidKeyException e) {
                        e.printStackTrace();
                    }
                });
                now += numEachPage;
                rows -= numEachPage;
            }
            excelWriter.write(list, writeSheet);
            list.clear();
        }
    }

    private Student randomStudent() throws InvalidKeyException {
        return new Student(
                null,
                RandomStringUtils.randomAlphanumeric(12),
                RandomStringUtils.randomAlphanumeric(6),
                AESUtils.encryptPlainTextToBase64(RandomStringUtils.randomNumeric(18), IdCardNumberEncryptConverter.KEY),
                LocalDateTime.now(),
                RandomStringUtils.randomAlphanumeric(12),
                randomStudentStatus(),
                new Faculties(RandomStringUtils.randomAlphanumeric(6)),
                new Major(RandomStringUtils.randomAlphanumeric(10)),
                new Classe(RandomStringUtils.randomAlphanumeric(10)),
                null,
                null,
                null
        );
    }

    private Student.Status randomStudentStatus(){
        switch (RandomUtils.nextInt(0, 2)){
            case 0:
                return Student.Status.FINE;
            case 1:
                return Student.Status.DROPOUT;
            default:
                return Student.Status.UNKNOWN;
        }
    }

    private class StudentDataImportAction implements Runnable {
        private final boolean allowMiscDataAutoInsert;
        private final File excelFile;
        private final int startRowNum;
        private final CountDownLatch countDownLatch;
        private final Map<String, List<String>> validateMsg;

        public StudentDataImportAction(boolean allowMiscDataAutoInsert, File excelFile, int startRowNum, CountDownLatch countDownLatch, Map<String, List<String>> validateMsg) {
            this.allowMiscDataAutoInsert = allowMiscDataAutoInsert;
            this.excelFile = excelFile;
            this.startRowNum = startRowNum;
            this.countDownLatch = countDownLatch;
            this.validateMsg = validateMsg;
        }

        @Override
        public void run() {
            StudentDataListener listener = new StudentDataListener(studentDAO, classeDAO, facultiesDAO, majorDAO, validator, validateMsg, allowMiscDataAutoInsert, startRowNum);
            EasyExcel.read(excelFile, Student.class, listener)
                    .headRowNumber(startRowNum)
                    .registerConverter(new LocalDateTimeConverter(validateMsg))
                    .sheet()
                    .doRead();
            countDownLatch.countDown();
        }
    }
}
